#include <rapidxml_print.hpp>
#include <signal.h>
#include <process.h>
#include <errno.h>

#include "common.h"
#include "NetworkManager.h"
#include "Task.h"
#include "Log.h"
#include "NodeManager.h"
#include "ClockManager.h"

#include <iostream>
using namespace std;
using namespace rapidxml;

#ifdef _WIN32
#ifdef _DEBUG
   #include <crtdbg.h>
   #undef THIS_FILE
   static char THIS_FILE[] = __FILE__;
   #define new       new( _NORMAL_BLOCK, __FILE__, __LINE__)
   #define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif

// global used to catch a ctrl-c signal (extern defined in 'stbd_t.h')
bool _intHandler = false;

// called when 'ctrl-c' is caught
void intHandler(int dummy = 0) {
	// global variable set when 'ctrl-c' is caught
	_intHandler = true;
}

// used to start thread, thread parameters
struct todo_params {
	HANDLE mutex;
	Task *task;
	Node *node;
	char *index;

	// in order to update complete status
	Block *block;
	int track_id;

	// if block is complete, process right away
	NetworkManager *network;
};

// used to start thread, thread function
unsigned __stdcall todo_thread(void *_params) {
	struct todo_params *params = (struct todo_params*)_params;

	// call start within module
	params->node->_start(params->mutex, params->task->pm, params->index);

	// immediately update the status of the thread
	WaitForSingleObject(params->mutex, INFINITE);	// Request ownership of mutex
	params->block->track_end(params->track_id);
	ReleaseMutex(params->mutex);	// Release the mutex so everyone can continue

	// if the block is complete, trigger so checkin can be processed right away
	if(params->block->isComplete()) {
		params->network->triggerWaitForReady();
	}

	// free up resources
	delete[] params->index;
	delete params;

	// end the thread
	_endthreadex(0);

	return 0;
}


int main(int argc, char *argv[]) {
	char buffer[DEFAULT_BUFLEN];
	sprintf(buffer, "logs\\drone_%d.log", GetCurrentProcessId());
	Log *log = new Log(buffer);
	NetworkManager *network = new NetworkManager();
	ParamManager *pm = new ParamManager();
	NodeManager *nm = new NodeManager();
	
	Task task = Task();
	ClockManager *clocks = new ClockManager();

	// create and initialize the thread mutex
	HANDLE mutex = CreateMutex(NULL, FALSE, NULL);

	char *next = NULL;	// next string from the network
	char data[DEFAULT_BUFLEN] = { "" };	// form strings to send to the network

	// catch process interrupt, in order to shutdown safely and save out state information
	signal(SIGINT, intHandler);

	// number of seconds to wait before adjusting threads to balance cpu utilization
	clocks->addCountdown("status_update", 5, network->hReady);
	clocks->start("status_update");

	// load 'port' from the settings file
	TCHAR _port[DEFAULT_BUFLEN] = { "" };
	GetPrivateProfileString(
		TEXT("default"),
		TEXT("port"),
		TEXT("2243"),	// default value if not found
		_port,
		DEFAULT_BUFLEN,
		TEXT(".\\stbd_t.ini"));

	// load 'host' from the settings file
	TCHAR _host[DEFAULT_BUFLEN] = { "" };
	GetPrivateProfileString(
		TEXT("default"),
		TEXT("host"),
		TEXT("127.0.0.1"),	// default value if not found
		_host,
		DEFAULT_BUFLEN,
		TEXT(".\\stbd_t.ini"));


	// Establish connection to the stbd_c server port
	Connection *conn = NULL;
	conn = network->add();
	conn->settings.set("host", _host);
	conn->settings.set("port", _port);
	if(!network->start(conn)) {
		log->write("step 14: network->start failed\n");
		exit(1);
	}


	// Identify as a task drone
	strcpy(data, "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
	sprintf(data, "%s<packet>", data);
	sprintf(data, "%s<cmd type=\"request\">ident_drone</cmd>", data);
	sprintf(data, "%s</packet>\r\n", data);
//	sprintf(data, "cmd=ident_drone\r\n");
	if(!conn->write(data)) { log->write("sendBytes failed\n"); exit(0); }

	// Continue forever, until interrupted with Ctrl-C
	while(!_intHandler) {

		// wait forever until network activity
		network->waitForReady();

		// Has our countdown to the next status update finished?
		if(clocks->isComplete("status_update")) {
//		if(false) {
			if(task.id) {
				/**
				 * <?xml version="1.0" encoding="ISO-8859-1"?>
				 * <packet>
				 *   <cmd type="request">status_set</cmd>
				 *   <drone>
				 *     <task_id>##</task_id>
				 *     <threads>##</threads>
				 *     <block>
				 *       <pending>##</pending>
				 *       <active>##</active>
				 *       <complete>##</complete>
				 *     </block>
				 *     ...
				 *     <block>
				 *       <pending>##</pending>
				 *       <active>##</active>
				 *       <complete>##</complete>
				 *     </block>
				 *   </drone>
				 * <packet>
				 */
				strcpy(data, "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
				sprintf(data, "%s<packet>", data);
				sprintf(data, "%s<cmd type=\"request\">status_set</cmd>", data);
				sprintf(data, "%s<drone>", data);
				sprintf(data, "%s<task_id>%s</task_id>", data, task.id);
				sprintf(data, "%s<threads>%d</threads>", data, task.threads);
				Block *block = task.bm.head;
				while(block) {
					sprintf(data, "%s<block>", data);
					sprintf(data, "%s<index>%d</index>", data, block->index);
					sprintf(data, "%s<pending>%d</pending>", data, block->getPending());
					sprintf(data, "%s<active>%d</active>", data, block->getActive());
					sprintf(data, "%s<complete>%d</complete>", data, block->getComplete());
					sprintf(data, "%s<threads>%d</threads>", data, task.threads);
					sprintf(data, "%s</block>", data);

					block = block->next;
				}
				sprintf(data, "%s</drone>", data);
				sprintf(data, "%s</packet>\n", data);
				network->write("port", "2243", data);
			}

			// reset for the next status update
			clocks->reset("status_update");
			clocks->start("status_update");
		}


		// process the network activity
		if(network->isNew()) {
			// this should never happen, the drone does not start server threads
			while(conn = network->getNewConnection()) {
				//ignore
			}
		}
		else if(network->isData()) {
			while(conn = network->getDataConnection()) {
				// Obtain the next network data to work with
				next = conn->getData();
#ifdef _DEBUG
//				sprintf(data, "[%d] Recv: %s\n", conn->socket, next);	log->write(data);
//				printf(data);
#endif // _DEBUG

				// parse recv network data
				pm->init(next, "|");

				if(!strcmp(pm->get("cmd"), "task_list_reply")) {
					// ignore
				}
				else if(!strcmp(pm->get("cmd"), "task_list_item")) {
					if(task.id == NULL) {
						task.id = _strdup(pm->get("id"));
						char *buffer = new char[strlen(pm->get("id")) + strlen("log\\task_") + 1];
						sprintf(buffer, "logs\\task_%s.log", pm->get("id"));
						log->open(buffer);
					}
				}
				else if(!strcmp(pm->get("cmd"), "task_detail_reply")) {
					// delete all the current details, as we are about to be re-told
					task.pm.clear();
				}
				else if(!strcmp(pm->get("cmd"), "task_detail_item")) {
					// associate the detail with the stored task
					task.pm.set(pm->get("data"), pm->get("value"));
				}
				else if(!strcmp(pm->get("cmd"), "task_get_reply")) {
					if(task.id == NULL) {
						task.id = _strdup(pm->get("id"));
					}

					// set task parameter
					task.pm.set("todo", pm->get("todo"));
					task.pm.set("priority", pm->get("priority"));
					// update the state of the task
					task.state = (State) atoi(pm->get("state"));
				}
				else if(!strcmp(pm->get("cmd"), "notice")) {
	//				printf("Notice: %s, %s\r\n", pm->get("id"), pm->get("info"));

					if(!strcmp(pm->get("info"), "1")) {
						// a task has begun
#ifdef _DEBUG
						sprintf(data, "[Notice] 1: (id=%s) Task is active: %s\r\n", pm->get("id"), pm->get("todo"));	log->write(data);
#endif // _DEBUG

						// a new task has been added, get basic task info
						sprintf(data, "cmd=task_get|id=%s\r\n", pm->get("id"));
						if(!conn->write(data)) { log->write("sendBytes failed\n"); }

						// load up detail related to task
						sprintf(data, "cmd=task_detail|id=%s\r\n", pm->get("id"));
						if(!conn->write(data)) { log->write("sendBytes failed\n"); }
					}
					else if(!strcmp(pm->get("info"), "2")) {
						// a task has finished
#ifdef _DEBUG
						sprintf(data, "[Notice] 2: (id=%s) Task is complete\r\n", pm->get("id"));	log->write(data);
#endif // _DEBUG
						// flag that the task has been deleted
						task.isShutdown = true;
					}
				}
				else if(!strcmp(pm->get("cmd"), "block_checkout_reply")) {
					if(!strcmp(pm->get("status"), "0")) {
						// remember block
						task.bm.add(pm->get("index"), atoi(pm->get("size")));
						task.isCheckout = false;
					}
					// task is either busy, or the task_id is unknown
					else {
						task.isCheckout = false;	
						task.t_curr = time(NULL);
						task.isBusy = true;
					}
				}
				else if(!strcmp(pm->get("cmd"), "thread_increase")) {
					// increase threads available by specified count
#ifdef _DEBUG
					printf("stbd_c> increase threads %d by %s\r\n", task.threads, pm->get("count"));
#endif // _DEBUG
					task.threads += atoi(pm->get("count"));
				}
				else if(!strcmp(pm->get("cmd"), "thread_decrease")) {
					// decrease threads available by specified count
#ifdef _DEBUG
					printf("stbd_c> decrease threads %d by %s\r\n", task.threads, pm->get("count"));
#endif // _DEBUG
					task.threads -= atoi(pm->get("count"));
				}
				else if(!strcmp(pm->get("cmd"), "block_checkin_reply")) {
					// ignore
				}
				else {
					// if able to base64 decode successfully, then this may be xml
					char text[DEFAULT_BUFLEN];

//					printf("xml parse: '%s'\n", next);
					strcpy(text, next);
					xml_document<> doc;
					try {
						doc.parse<0>(text);
					
						// standard way to use rapidxml, though there will only ever be one packet
						if(doc.first_node("packet") && doc.first_node("packet")->first_node("cmd")) {	// validate
							xml_node<> *packet = doc.first_node("packet");
							xml_node<> *cmd = packet->first_node("cmd");

//							cout << "(XML) cmd = '" << cmd->value() << "'" << endl;
				

// Is this communication from a drone?
if(!strcmp(cmd->value(), "task_detail") && !strcmp( (cmd->first_attribute("type", 0, false))->value(), "response") ) {
//	printf("xml: task_detail\n");

	if(packet->first_node("task_detail")) {	// validate		
		// delete all the current details, as we are about to be re-told
		task.pm.clear();

//		printf("t(task_detail)->detail {\n");
		xml_node<> *task_detail = packet->first_node("task_detail");
		for(xml_node<> *xdetail = task_detail->first_node(); xdetail; xdetail = xdetail->next_sibling()) {
//			printf("  %s = %s\n", xdetail->name(), xdetail->value());

			// associate the detail with the stored task
			task.pm.set(xdetail->name(), xdetail->value());
		}

//		printf("}\n");
	}
}
else if(!strcmp(cmd->value(), "task_get") && !strcmp( (cmd->first_attribute("type", 0, false))->value(), "response") )  {
//	printf("xml: task_get\n");
	if(packet->first_node("task") && packet->first_node("id")) {	// validate
		xml_node<> *xtask = packet->first_node("task");

		for(xml_node<> *xdetail = xtask->first_node(); xdetail; xdetail = xdetail->next_sibling()) {
			// set task parameter
			task.pm.set(xdetail->name(), xdetail->value());

			if(!stricmp(xdetail->name(), "state")) {	
				// update the state of the task
				task.state = (State) atoi(xdetail->value());
			}
			else if(!stricmp(xdetail->name(), "id")) {	
				// remember the task id
				task.id = _strdup(packet->first_node("id")->value());
			}
		}
	}
}
else if(!strcmp(cmd->value(), "block_checkout") && !strcmp( (cmd->first_attribute("type", 0, false))->value(), "response") )  {
//	printf("xml: block_checkout\n");
	//	printf("xml: task_get\n");
	if(cmd->first_attribute("status", 0, false) && packet->first_node("id")) {	// validate
		if(!stricmp(cmd->first_attribute("status", 0, false)->value(), "0")) {
			if(packet->first_node("block")->first_node("index")
				&& packet->first_node("block")->first_node("size")) {	// validate

				xml_node<> *xblock = packet->first_node("block");
//				printf("remembering block: %s, %d\n", xblock->first_node("index")->value(), atoi(xblock->first_node("size")->value()));

				// remember block
				task.bm.add(xblock->first_node("index")->value(), atoi(xblock->first_node("size")->value()));
				task.isCheckout = false;
			}
		}
		// task is either busy, or the task_id is unknown
		else {
//			printf("BUSY!!!\n");
			task.isCheckout = false;	
			task.t_curr = time(NULL);
			task.isBusy = true;
		}
	}
}
else if(!strcmp(cmd->value(), "block_checkin") && !strcmp( (cmd->first_attribute("type", 0, false))->value(), "response") )  {
//	printf("xml: block_checkin\n");

	// ignore
}
/*
else if(!strcmp(cmd->first_attribute("type", 0, false)->value(), "notice")
	&& !strcmp(cmd->value(), "task_start")
	&& packet->first_node("id")
	&& packet->first_node("todo")
	)  {
	printf("task_start (%s)\n", packet->first_node("id")->value());

	Task *task = tm->get( packet->first_node("id")->value() );
	if(task == NULL) {
		tm->add(packet->first_node("id")->value(), PENDING);
		task = tm->get( packet->first_node("id")->value() );
	}

	// forward the data to the drone
	sprintf(data, "%s\r\n", next);
	network->write("task_id", packet->first_node("id")->value(), data);
}
*/
else if(!strcmp(cmd->first_attribute("type", 0, false)->value(), "notice")
	&& !strcmp(cmd->value(), "task_complete"))  {
//	printf("xml: task_complete\n");

	// flag that the task has been deleted
	task.isShutdown = true;
}


						}
					} catch(rapidxml::parse_error code) {
						cout << "Parse error failure" << endl;
					}
				}

				// move to next string to process
				conn->bm->pop();
			}

			// Set the isReady event to signaled (so we can get right to block_checkin, thread_complete)
//			network->triggerWaitForReady();
		}
		else if(network->isDel()) {
			while(conn = network->getDelConnection()) {
				// free up the resources of the dropped connection
				network->del(conn);
			}
			// exit, an alternative would be to try reconnecting to the server
			break;
		}

		// Has a task been flagged for delete?  Ignore until block finishes, then delete.
		if(task.isShutdown) {
			_intHandler = 1;
		}

		// check in finished blocks
		Block *block = task.bm.head;
		while(block) {
			if(!block->isComplete()) {
				// if a thread has completed notify stbd_c, & flag so there isn't a 2nd notification
				// block.status[#] = 0 pending, 1 active, 2 complete
				for(int ctr = 0; ctr < block->size; ctr ++) {
					// if there is a thread pending, start
					if(block->status[ctr] == 0) {
						// start thread, if there is a thread available
						if(task.threads > task.bm.getActive()) {
							// locate node to process type of task
							Node *node = nm->get(task.pm.get("todo"));
							if(node == NULL) {
								// load up just this one node - this only occurs once
								sprintf(data, "nodes\\n_%s.dll", task.pm.get("todo"));
								// todo: verify the file exists before trying to add
								nm->add(data);

								// locate node to process type of task
								node = nm->get(task.pm.get("todo"));
								if(node == NULL) {
									// failed again, ok, just load everything, see if that works
									nm->load();

									// locate node to process type of task
									node = nm->get(task.pm.get("todo"));
									if(node == NULL) {
										// abort
										log->write("unable to load node\n");
										exit(0);
									}
								}
							}

							// form thread parameters, the task and the module
							struct todo_params *params = new struct todo_params;
							params->mutex = mutex;
							params->task = &task;
							params->node = node;
							char *ptr = task.bm.getNextIndex();
							params->index = new char[strlen(ptr) + 1];
							sprintf(params->index, "%s", ptr);

							// to allow for immediate status update upon completion
							params->block = block;
							params->track_id = ctr;
							// so network can be triggered for checkin if block is ready
							params->network = network;

							// start thread
							unsigned threadId;
							block->handles[ctr] = (HANDLE)_beginthreadex(NULL, 0, &todo_thread, params, 0, &threadId);

							// before storing for tracking purposes, check for error cases
							if((int)block->handles[ctr] == 0) {
								sprintf(data, "BlockManager::track(HANDLE): unable to start thread: %d\r\n",
										(int)block->handles[ctr]);	log->write(data);
								switch(errno) {
									case EAGAIN:
										log->write("errno: too many threads\r\n");
										break;
									case EINVAL:
										log->write("errno: the argument is invalid or the stack size is incorrect\r\n");
										break;
									case EACCES:
										log->write("errno: insufficient resources (such as memory)\r\n");
										break;
									case ENOMEM:
										log->write("errno: Not enough memory\r\n");
										break;
									default:
										sprintf(data, "errno: unknown (%d, %d)\r\n", errno, _doserrno);	log->write(data);
										break;
								}
								// thread failed, sleep for a bit then try again
//										sprintf(data, "Thread failed\n");
//										log->write(data);

								printf("thread failed\n");
								Sleep(5000);
							}
							else {
								// thread started, adjust tracking information
								WaitForSingleObject(mutex, INFINITE);	// Request ownership of mutex
								block->status[ctr] = 1;	// set index to active
								-- block->counts[0];	// one less pending
								++ block->counts[1];	// one more active
								ReleaseMutex(mutex);	// Release the mutex for everyone to continue

								network->triggerWaitForReady();	// allows to launch another thread right away
								break;
							}
						}
					}
				}
				block = block->next;
			}
			else {
				// send block checkin
				strcpy(data, "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
				sprintf(data, "%s<packet>", data);
				sprintf(data, "%s<cmd type='request'>block_checkin</cmd>", data);
				sprintf(data, "%s<id>%s</id>", data, task.id);
				sprintf(data, "%s<index>%s</index>", data, block->index);
				sprintf(data, "%s</packet>\r\n", data);
//				sprintf(data, "cmd=block_checkin|id=%s|index=%s\r\n", task.id, block->index);
				network->write("port", _port, data);

				// free up memory related to finished block
				Block *tmp = block;
				block = block->next;
				task.bm.del(tmp);
			}
		}	// end start thread / checkin blocks

		// If the task is ACTIVE, and we do not have a block, check one out
		if(task.state == ACTIVE) {
			if(task.bm.head == NULL) {
				if(!task.isCheckout) {
					if(task.isBusy) {
						// has the time passed?
						if((time(NULL) - task.t_curr) > 20) {
							task.isBusy = false;	// next go around the block will checkout
						}
					}
					else {
						strcpy(data, "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
						sprintf(data, "%s<packet>", data);
						sprintf(data, "%s<cmd type='request'>block_checkout</cmd>", data);
						sprintf(data, "%s<id>%s</id>", data, task.id);
						sprintf(data, "%s</packet>\r\n", data);
//						sprintf(data, "cmd=block_checkout|id=%s\r\n", task.id);
						network->write("port", _port, data);
						task.isCheckout = true;
					}
				}
			}
		}

		// Catch a Ctrl-C interrupt
		if(_intHandler) {
			break;
		}

	}	// end forever

	// free up memory
	log->write("Exiting\r\n");
	delete clocks;
	delete log;
	delete nm;
	delete pm;
	delete network;

#ifdef _WIN32
#ifdef _DEBUG
	// Check for Memory Leaks
	_CrtDumpMemoryLeaks();
#endif
#endif
}